package com.qiongqi.datadir.service.impl;

import com.bettercloud.vault.VaultException;
import com.qiongqi.datadir.config.DataDirConfig;
import com.qiongqi.datadir.domain.BucketData;
import com.qiongqi.datadir.domain.BucketQuota;
import com.qiongqi.datadir.helper.VaultHelper;
import com.qiongqi.datadir.mapper.BucketQuotaMapper;
import com.qiongqi.datadir.service.IMinioService;
import io.minio.*;
import io.minio.errors.ErrorResponseException;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import paillierp.Paillier;
import paillierp.key.PaillierPrivateThresholdKey;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.time.Instant;
import java.util.*;

import static com.qiongqi.datadir.domain.BucketQuota.createDefault;
import static com.qiongqi.datadir.utils.security.HEUtils.createThresholdPaillierKey;
import static com.qiongqi.datadir.utils.uuid.IdUtils.fastSimpleUUID;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.apache.commons.collections4.CollectionUtils.size;
import static org.apache.commons.collections4.IterableUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.*;

@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class MinioServiceImpl implements IMinioService {

    @Autowired
    private DataDirConfig dataDirConfig;

    @Autowired
    private BucketQuotaMapper bucketQuotaMapper;

    @Autowired
    private MinioClient client;

    @Autowired
    private VaultHelper vaultHelper;

    /**
     * 创建 Bucket（默认无配额）
     *
     * @param bucketName
     * @return
     */
    @Override
    public boolean createBucket(String bucketName) {
        return createBucketWithQuota(bucketName, -1); // -1 表示无限制
    }

    /**
     * 创建 Bucket 并设置配额（单位：字节）
     *
     * @param bucketName
     * @param quotaBytes
     * @return
     */
    @Override
    public boolean createBucketWithQuota(String bucketName, long quotaBytes) {
        if (isBlank(bucketName)) {
            return false;
        }
        BucketQuota bq = new BucketQuota();
        bq.setName(bucketName);
        bq.setQuota(quotaBytes);
        bq.setCurrentSize(0);
        return createBucketWithQuota(bq);
    }

    /**
     * 创建 Bucket 并设置配额（单位：字节）
     *
     * @param bucketQuota
     * @return
     */
    @Override
    public boolean createBucketWithQuota(BucketQuota bucketQuota) {
        if (nonNull(bucketQuota)) {
            try {
                // 在内存中注册该 Bucket 的配额信息
                BucketQuota bq = bucketQuotaMapper.selectBucketQuotaByName(bucketQuota.getName());
                if (nonNull(bq)) {
                    log.error("{} has already already existed", bucketQuota.getName());
                    return false;
                }
                // 创建可信空间存储
                if (!client.bucketExists(BucketExistsArgs.builder().bucket(bucketQuota.getName()).build())) {
                    client.makeBucket(MakeBucketArgs.builder().bucket(bucketQuota.getName()).build());
                }

                // 创建可信空间密钥
                Paillier paillier = storeParticipantsSecret(bucketQuota.getName(), bucketQuota.getParticipants());
                if (isNull(paillier)) {
                    log.warn("Failed to store paillier in vault:{}", bucketQuota);
                }

                Instant currentTime = Instant.now();
                bucketQuota.setId(fastSimpleUUID());
                bucketQuota.setCreateTime(currentTime);
                bucketQuota.setCreateBy(dataDirConfig.getName());
                bucketQuota.setUpdateTime(currentTime);
                bucketQuota.setUpdateBy(dataDirConfig.getName());
                int ret = bucketQuotaMapper.insertBucketQuota(bucketQuota);
                log.info("Bucket '{}' is created with quota: {} GB", bucketQuota.getName(), bucketQuota.getQuota() == -1 ? "unlimited" : bucketQuota.getQuota());
                return ret > 0;
            } catch (Exception e) {
                log.error("Failed to create bucket: {}", bucketQuota, e);
                return false;
            }
        }
        return false;
    }

    /**
     * 设置现有 Bucket 的配额
     *
     * @param bucketName
     * @param quotaBytes
     * @return
     */
    @Override
    public boolean setBucketQuota(String bucketName, long quotaBytes) {
        BucketQuota bq = bucketQuotaMapper.selectBucketQuotaByName(bucketName);
        if (nonNull(bq)) {
            bq.setQuota(quotaBytes);
            bq.setUpdateBy(dataDirConfig.getName());
            bq.setUpdateTime(Instant.now());
            log.info("Bucket '{}' quota updated to: {} bytes", bucketName, quotaBytes == -1 ? "unlimited" : quotaBytes);
            int ret = bucketQuotaMapper.updateBucketQuota(bq);
            return ret > 0;
        } else {
            log.warn("Bucket '{}' does not exist", bucketName);
            return false;
        }
    }

    /**
     * 获取 Bucket 配额信息
     *
     * @param bucketName
     * @return
     */
    @Override
    public Optional<BucketQuota> getBucketQuota(String bucketName) {
        return Optional.ofNullable(bucketQuotaMapper.selectBucketQuotaByName(bucketName));
    }

    /**
     * 上传文件（带配额检查）
     *
     * @param bucketName
     * @param bucketData
     * @param stream
     * @param size
     * @param contentType
     * @throws Exception
     */
    @Override
    public int putObject(String bucketName, BucketData bucketData, InputStream stream, long size, String contentType) throws Exception {
        int ret = -1;
        BucketQuota quota = bucketQuotaMapper.selectBucketQuotaByName(bucketName);
        Instant currentTime = Instant.now();
        boolean isNew = false;
        if (isNull(quota)) {
            quota = createDefault(bucketName);
            quota.setCreateBy(dataDirConfig.getName());
            quota.setCreateTime(currentTime);
            isNew = true;
        }
        // 🔒 检查配额
        if (quota.isOverQuota(size)) {
            throw new IllegalStateException(
                    String.format("Upload rejected: Bucket '%s' would exceed quota. " +
                                    "Current: %d bytes, Quota: %d GB, Adding: %d bytes",
                            bucketName, quota.getCurrentSize(), quota.getQuota(), size)
            );
        }
        try {
            client.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(bucketData.getResName())
                    .contentType(contentType)
                    .stream(stream, size, -1)
                    .build());
            // 上传成功，更新大小
            quota.increase(size);
            quota.setUpdateBy(dataDirConfig.getName());
            quota.setUpdateTime(currentTime);
            List<BucketData> datas = quota.getDatas();
            if (isEmpty(datas)) {
                datas = new ArrayList<>();
            }
            datas.add(bucketData);
            quota.setDatas(datas);
            if (isNew) {
                ret = bucketQuotaMapper.insertBucketQuota(quota);
            } else {
                ret = bucketQuotaMapper.updateBucketQuota(quota);
            }
            log.info("Object uploaded: bucket='{}', object='{}', size={} bytes, ret={}", bucketName, bucketData.getResName(), size, ret);
        } catch (Exception e) {
            log.error("Failed to upload object: bucket={}, object={}", bucketName, bucketData.getResName(), e);
            throw e;
        }
        return ret;
    }

    /**
     * 删除对象（更新大小）
     *
     * @param bucketName
     * @param objectName
     * @throws Exception
     */
    @Override
    public void removeObject(String bucketName, String objectName) throws Exception {
        StatObjectResponse stat = null;
        try {
            stat = client.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (ErrorResponseException e) {
            if (equalsIgnoreCase(e.errorResponse().code(), "NoSuchKey")) {
                log.warn("Object not found: bucket={}, object={}", bucketName, objectName);
                return; // 对象不存在，无需删除
            }
            throw e;
        }

        // 先删除，再更新大小（避免大小不一致）
        client.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());

        // 删除后减去大小
        BucketQuota quota = bucketQuotaMapper.selectBucketQuotaByName(bucketName);
        int ret = -1;
        if (nonNull(quota)) {
            quota.decrease(stat.size());
            List<BucketData> datas = quota.getDatas();
            if (isNotEmpty(datas)) {
                datas.removeIf(data -> equalsIgnoreCase(data.getResName(), objectName));
            }
            quota.setDatas(datas);
            quota.setUpdateBy(dataDirConfig.getName());
            quota.setUpdateTime(Instant.now());
            ret = bucketQuotaMapper.updateBucketQuota(quota);
        }
        log.info("Object removed: bucket='{}', object='{}', size={} bytes, ret={}", bucketName, objectName, stat.size(), ret);
    }

    /**
     * 获取所有 Buckets（带配额信息）
     *
     * @return
     */
    @Override
    public List<Map<String, Object>> getAllBucketsWithInfo() {
        List<Map<String, Object>> result = new ArrayList<>();
        try {
            List<Bucket> buckets = client.listBuckets();
            for (Bucket bucket : buckets) {
                BucketQuota quota = bucketQuotaMapper.selectBucketQuotaByName(bucket.name());
                if (isNull(quota)) {
                    quota = createDefault(bucket.name());
                }
                Map<String, Object> info = new HashMap<>();
                info.put("bucket", bucket);
                info.put("currentSize", quota.getCurrentSize());
                info.put("quota", quota.getQuota());
                info.put("overQuota", quota.isOverQuota(0));
                result.add(info);
            }
        } catch (Exception e) {
            log.error("Failed to get all buckets with info", e);
        }
        return result;
    }

    /**
     * 获取所有 Buckets（不带配额信息）
     *
     * @return
     */
    @Override
    public List<Bucket> getAllBuckets() {
        try {
            return client.listBuckets();
        } catch (Exception e) {
            log.error("Failed to get all buckets", e);
            return Collections.emptyList();
        }
    }

    /**
     * 根据名称获取桶信息
     *
     * @param bucketName
     * @return
     */
    @Override
    public Optional<Bucket> getBucket(String bucketName) {
        try {
            return client.listBuckets().stream()
                    .filter(b -> equalsIgnoreCase(b.name(), bucketName))
                    .findFirst();
        } catch (Exception e) {
            log.error("Failed to get bucket: {}", bucketName, e);
            return Optional.empty();
        }
    }

    /**
     * 根据名称删除桶
     *
     * @param bucketName
     */
    @Override
    public void removeBucket(String bucketName) {
        try {
            client.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
            int ret = bucketQuotaMapper.deleteBucketQuotaByName(bucketName);
            log.info("Bucket '{}' removed and quota info cleared, ret={}", bucketName, ret);
        } catch (Exception e) {
            log.error("Failed to remove bucket: {}", bucketName, e);
        }
    }

    /**
     * 根据桶名与对象前缀获取对象列表
     *
     * @param bucketName
     * @param prefix
     * @param recursive
     * @return
     */
    @Override
    public List<Item> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
        List<Item> list = new ArrayList<>();
        try {
            Iterable<Result<Item>> results = client.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build()
            );
            for (Result<Item> result : results) {
                list.add(result.get());
            }
        } catch (Exception e) {
            log.error("Failed to list objects: bucket={}, prefix={}", bucketName, prefix, e);
        }
        return list;
    }

    /**
     * 获取对象URL
     *
     * @param bucketName
     * @param objectName
     * @param expires
     * @param method
     * @param reqParams
     * @return
     */
    @Override
    public String getObjectURL(String bucketName, String objectName, Integer expires, Method method, Map<String, String> reqParams) {
        try {
            return client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .bucket(bucketName).object(objectName).method(method).expiry(expires)
                    .extraQueryParams(reqParams).build());
        } catch (Exception e) {
            log.error("Failed to generate presigned URL: bucket={}, object={}", bucketName, objectName, e);
            return null;
        }
    }

    /**
     * 根据桶名称和对象名称获取存储对象
     *
     * @param bucketName
     * @param objectName
     * @return
     */
    @Override
    public InputStream getObject(String bucketName, String objectName) {
        try {
            return client.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            log.error("Failed to get object: bucket={}, object={}", bucketName, objectName, e);
            return null;
        }
    }

    /**
     * 根据桶名称和对象名称获取对象信息
     *
     * @param bucketName
     * @param objectName
     * @return
     * @throws Exception
     */
    @Override
    public StatObjectResponse getObjectInfo(String bucketName, String objectName) throws Exception {
        return client.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 简化上传（自动获取对象大小）
     *
     * @param bucketName
     * @param bucketData
     * @param inputFile
     * @throws Exception
     */
    @Override
    public int putObject(String bucketName, BucketData bucketData, File inputFile) throws Exception {
        InputStream stream = new FileInputStream(inputFile);
        return putObject(bucketName, bucketData, stream, stream.available(), "application/octet-stream");
    }

    /**
     * 获取所有 Buckets（过期的）
     *
     * @return
     */
    @Override
    public List<BucketQuota> getAllExpiredBuckets() {
        return List.of();
    }

    /**
     * 清空与删除桶
     *
     * @param bucketName
     * @return
     */
    @Override
    public boolean removeBucketByName(String bucketName) {
        try {
            boolean exists = client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!exists) {
                log.warn("存储桶{}不存在", bucketName);
                return true;
            }
            // 列出桶中的所有对象
            Iterable<Result<Item>> myObjects = client.listObjects(ListObjectsArgs.builder().bucket(bucketName).recursive(true).build());
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                // 删除对象
                client.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(item.objectName()).build());
            }
            // 删除桶（在清空对象后）
            client.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
            return true;
        } catch (Exception ex) {
            log.error("Failed to remove bucket by name:{}", ex);
            return false;
        }
    }

    private Paillier storeParticipantsSecret(String bucketName, List<String> parts) throws VaultException {
        Paillier publicKey = null;
        int partLen = size(parts);
        if (isNotBlank(bucketName) && partLen > 0) {
            PaillierPrivateThresholdKey[] partKeys = createThresholdPaillierKey(partLen);
            for (int idx = 0; idx < partLen; idx++) {
                String shareName = IterableUtils.get(parts, idx);
                PaillierPrivateThresholdKey partKey = ArrayUtils.get(partKeys, idx);
                vaultHelper.storePrivateKeyShare(bucketName, shareName, partKey);
                if (idx == (partLen - 1)) {
                    publicKey = new Paillier(partKey.extractPublicKey());
                    vaultHelper.storePublicKeyShare(bucketName, publicKey);
                }
            }
        }
        return publicKey;
    }
}
